package niuniu

import (
	"fmt"
	"gitee.com/yuya000/cards-game-server-go/logwarp"
	"gitee.com/yuya000/cards-game-server-go/server"
	"github.com/garyburd/redigo/redis"
	"github.com/shopspring/decimal"
	"github.com/xormplus/xorm"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	StatusWaiting            = iota // 游戏准备中
	StatusStart                     // 游戏开始
	StatusPrepareQiangZhuang        // 预备抢庄
	StatusXiaZhu                    // 下注状态
	StatusFaPai                     // 发牌
	StatusChuPai                    // 出牌
	StatusJieSuan                   // 结算
	StatusExit                      // 游戏退出
	StatusDestroy                   // 游戏注销
)

type ChuPaiCombination struct {
	ChuPaiMap     map[Pai]string
	SurplusPaiMap map[Pai]string
	Niu           int
}

type NiuNiu struct {
	// 游戏ID
	id string
	// 游戏名称
	name string
	// 当前回合
	Round int
	// 总回合
	MaxRound int
	// 游戏配置信息
	Conf *NiuniuConf
	// 花费房卡数
	CostCards int
	// 房间所属人
	Owner *server.Player
	// 抢庄规则
	QiangZhuang bool
	// 启用金牛规则
	Jinniu bool
	// 启用银牛规则
	Yinniu bool
	// 启用比花色规则
	Bihuase bool
	// 庄主
	ZhuangZhu *server.Player
	// 游戏状态
	Stat int
	// 玩家
	players *server.PlayerMap
	// 游戏创建参数
	args server.Args
	// 准备好的玩家ID列表
	ReadyPlayers *server.PlayerMap
	// 抢庄用户
	QiangZhuangPlayer []*server.Player
	// 下注
	Stake map[string]int
	// 玩家持有的牌
	PlayerHoldingPais map[string]map[Pai]string
	// 出牌
	ChuPai map[string]*ChuPaiCombination
	// 投票解散
	GameExitBallotBox map[string]string
	mutex             sync.Mutex
	gameOver          bool
}

func (niuniu *NiuNiu) Id() string {
	return niuniu.id
}

func (niuniu *NiuNiu) Name() string {
	return niuniu.name
}

func (niuniu *NiuNiu) JoinGame(player *server.Player) server.Exception {
	niuniu.mutex.Lock()
	defer niuniu.mutex.Unlock()
	if niuniu.Stat > StatusStart || niuniu.Round > 1 {
		return GameStartException
	}
	if niuniu.players.Len() >= niuniu.Conf.MaxPlayerN {
		return PlayerFullException
	}
	logwarp.Std().Debugln("群推玩家加入信息", player.Id, niuniu.players)
	niuniu.Broadcast(player, MessageTypePlayerJoinEvent, &PlayerJoinNotice{
		UserInfo: player.UserInfo.ToUserInfoEntry(),
		UserClub: player.CurrentClub.ToClubInfoEntry(),
	})
	// 发送游戏类型
	player.Unicast(&server.GameTypeResp{GameType: niuniu.Name()}, server.MessageTypeGameType)

	logwarp.Std().Debugln("发送加入游戏的响应", player.Id)
	niuniu.args.Store("cost", []int{niuniu.CostCards})
	player.Unicast(&JoinGameResp{
		GameType:      niuniu.Name(),
		HouseNo:       niuniu.Id(),
		Players:       niuniu.players.ToUsers(),
		PlayerClubs:   niuniu.players.ToClubs(),
		GameBuildArgs: niuniu.args,
		ReadyPlayers:  niuniu.ReadyPlayers.Keys(),
	}, server.MessageTypeJoinGame)
	niuniu.players.Store(player.Id, player)
	return nil
}

func (niuniu *NiuNiu) ResumeGame(aPlayer *server.Player) server.Exception {
	niuniu.mutex.Lock()
	defer niuniu.mutex.Unlock()
	player, exist := niuniu.players.Load(aPlayer.Id)
	if !exist {
		return server.InvalidOptException
	}
	player.Session = aPlayer.Session
	player.UserInfo = aPlayer.UserInfo
	player.CurrentClub = aPlayer.CurrentClub
	var (
		ZhuangZhuId string
	)
	if niuniu.ZhuangZhu != nil {
		ZhuangZhuId = niuniu.ZhuangZhu.Id
	}
	player.Unicast(&PlayerResumeEvent{
		GameType:            niuniu.Name(),
		No:                  niuniu.Id(),
		Status:              niuniu.Stat,
		Rules:               niuniu.args,
		Owner:               niuniu.Owner.Id,
		ZhuangZhu:           ZhuangZhuId,
		MaxRound:            niuniu.MaxRound,
		CurrentRound:        niuniu.Round,
		ReadyPlayer:         niuniu.ReadyPlayers.Keys(),
		Stake:               niuniu.Stake,
		QiangZhuangPlayer:   GetPlayerSliceIds(niuniu.QiangZhuangPlayer),
		PlayerHoldingPai:    PaisMapToStringKeys(niuniu.PlayerHoldingPais[player.Id]),
		GameExitBallotBox:   niuniu.GameExitBallotBox,
		Players:             niuniu.players.ToUsers(),
		PlayersCurrentScore: GetPlayerCurrentScores(niuniu.players),
		PlayersTotalScore:   GetPlayerTotalScores(niuniu.players),
		PlayersClubInfo:     PlayersToUserInfos(niuniu.players.ToClubs()),
		LostPlayer:          GetLostPlayer(niuniu.players),
	}, server.MessageTypePlayerResume)
	niuniu.Broadcast(player, MessageTypePlayerResume, &PlayerResumeNoticeEvent{Id: player.Id})
	return nil
}

func GetLostPlayer(players *server.PlayerMap) []string {
	var ret []string
	players.Foreach(func(id string, player *server.Player) {
		if player.Stat == server.PlayerLost {
			ret = append(ret, player.Id)
		}
	})
	return ret
}

func PlayersToUserInfos(players []*server.ClubInfoEntry) map[string]*server.ClubInfoEntry {
	var ret = make(map[string]*server.ClubInfoEntry)
	for _, player := range players {
		ret[player.Id] = player
	}
	return ret
}

func GetPlayerCurrentScores(players *server.PlayerMap) map[string]int {
	var ret = make(map[string]int)
	players.Foreach(func(s string, player *server.Player) {
		ret[player.Id] = player.CurrentScore
	})
	return ret
}

func GetPlayerTotalScores(players *server.PlayerMap) map[string]int {
	var ret = make(map[string]int)
	players.Foreach(func(s string, player *server.Player) {
		ret[player.Id] = player.RoundOfScore
	})
	return ret
}

func GetPlayerSliceIds(players []*server.Player) []string {
	var retval []string
	for _, p := range players {
		retval = append(retval, p.Id)
	}
	return retval
}

func (niuniu *NiuNiu) ready(player *server.Player) {
	logwarp.Std().Infoln("ready()")
	if niuniu.Round <= 1 { // 第一把
		if niuniu.Stat != StatusWaiting || player == niuniu.Owner {
			player.SendException(server.InvalidOptException, MessageTypeReadyEvent)
			return
		}
	} else if niuniu.Stat != StatusWaiting { // 非第一把
		player.SendException(server.InvalidOptException, MessageTypeReadyEvent)
		return
	}
	logwarp.Std().Debugln("ReadyPlayer", niuniu.ReadyPlayers)
	if niuniu.ReadyPlayers.Exist(player.Id) {
		return
	}
	niuniu.ReadyPlayers.Store(player.Id, player)
	// 通知其他玩家准备好了
	niuniu.Broadcast(player, MessageTypeReadyEvent, &ReadyEvent{Id: player.Id})
	if niuniu.Round > 1 { // 非第一把
		logwarp.Std().Infoln("非第一把")
		if niuniu.ReadyPlayers.Len() == niuniu.players.Len() {
			niuniu.Stat = StatusStart
			niuniu.gameStart(niuniu.Owner)
		}
	}
}

func (niuniu *NiuNiu) gameStart(player *server.Player) {
	logwarp.Std().Infoln("gameStart() 1")
	if !niuniu.Owner.Eq(player) {
		player.SendException(server.InvalidOptException, MessageTypeGameStart)
		return
	}
	if niuniu.Round <= 1 && niuniu.ReadyPlayers.Len() == niuniu.players.Len()-1 {
		if niuniu.ReadyPlayers.Len() == niuniu.players.Len()-1 {
			niuniu.Stat = StatusStart
		} else {
			player.SendException(server.InvalidOptException, MessageTypeGameStart)
			return
		}
	}
	logwarp.Std().Infoln("gameStart() 2")
	// 游戏合法人数判断
	if niuniu.players.Len() < niuniu.Conf.MinPlayerN || niuniu.players.Len() > niuniu.Conf.MaxPlayerN {
		player.SendException(server.InvalidOptException, MessageTypeGameStart)
		return
	}
	logwarp.Std().Infoln("gameStart() 3", niuniu.ReadyPlayers.Len(), niuniu.players.Len())
	// 准备状态判断
	if niuniu.ReadyPlayers.Len() < niuniu.players.Len()-1 {
		player.SendException(server.InvalidOptException, MessageTypeGameStart)
		return
	}
	logwarp.Std().Infoln("gameStart() 4")

	// 游戏开始群通知
	niuniu.Broadcast(nil, MessageTypeGameStart, nil)

	// 抢庄坐庄判断
	if niuniu.QiangZhuang {
		niuniu.Broadcast(nil, MessageTypePrepareQiangZhuang, nil)
		niuniu.Stat = StatusPrepareQiangZhuang
		go func() {
			time.Sleep(time.Second * 10)
			niuniu.qiangZhuangTimeout()
		}()
	} else { // 进入下注阶段
		niuniu.ZhuangZhu = niuniu.Owner
		niuniu.Broadcast(nil, MessageTypeZhuangZhu, &ZhuangZhuEvent{
			Id: niuniu.ZhuangZhu.Id,
		})
		niuniu.Broadcast(nil, MessageTypeXiaZhu, nil)
		niuniu.Stat = StatusXiaZhu
	}
}

func (niuniu *NiuNiu) qiangZhuangTimeout() {
	logwarp.Std().Infoln("qiangZhuangTimeout()")
	if niuniu.Stat != StatusPrepareQiangZhuang {
		return
	}
	if len(niuniu.QiangZhuangPlayer) == 0 {
		pids := niuniu.players.Keys()
		niuniu.ZhuangZhu, _ = niuniu.players.Load(pids[rand.Intn(niuniu.players.Len())])
	} else if len(niuniu.QiangZhuangPlayer) == 1 {
		niuniu.ZhuangZhu = niuniu.QiangZhuangPlayer[0]
	} else {
		niuniu.ZhuangZhu = niuniu.QiangZhuangPlayer[rand.Intn(len(niuniu.QiangZhuangPlayer))]
	}
	niuniu.Broadcast(nil, MessageTypeZhuangZhu, &ZhuangZhuEvent{Id: niuniu.ZhuangZhu.Id})
	niuniu.Stat = StatusXiaZhu
}

func (niuniu *NiuNiu) playerQuit(player *server.Player) {
	logwarp.Std().Infoln("playerQuit()")
	if niuniu.Stat > StatusStart {
		player.SendException(server.InvalidOptException, MessageTypePlayerQuit)
		return
	}
	if player.Eq(niuniu.Owner) {
		niuniu.killAllPlayer()
		niuniu.gameOver = true
		return
	}
	niuniu.ReadyPlayers.Del(player.Id)
	niuniu.players.Del(player.Id)
	player.Destroy()
	niuniu.Broadcast(nil, MessageTypePlayerQuit, &PlayerQuitEvent{Id: player.Id})
}

func (niuniu *NiuNiu) killAllPlayer() {
	niuniu.players.Foreach(func(s string, player *server.Player) {
		player.Destroy()
	})
}

func (niuniu *NiuNiu) gameDestroy(player *server.Player) {
	logwarp.Std().Infoln("gameDestroy")
	if !player.Eq(niuniu.Owner) {
		player.SendException(server.InvalidOptException, MessageTypeGameDestroy)
		return
	}
	if niuniu.Stat >= StatusStart {
		player.SendException(GameStartException, MessageTypeGameDestroy)
		return
	}
	niuniu.Stat = StatusDestroy
	niuniu.gameOver = true
	niuniu.Broadcast(player, MessageTypeGameDestroy, nil)
	player.Unicast(nil, MessageTypeGameDestroy)
	niuniu.killAllPlayer()
}

func (niuniu *NiuNiu) qiangZhuang(player *server.Player) {
	logwarp.Std().Infoln("qiangZhuang")
	if niuniu.ZhuangZhu != nil {
		return
	}
	if niuniu.Stat != StatusPrepareQiangZhuang {
		player.SendException(server.InvalidOptException, MessageTypeQiangZhuang)
		return
	}
	niuniu.QiangZhuangPlayer = append(niuniu.QiangZhuangPlayer, player)
	niuniu.Broadcast(player, MessageTypeQiangZhuang, &QiangZhuangEvent{Id: player.Id})
}

func (niuniu *NiuNiu) xiaZhu(player *server.Player, msg *server.Message) {
	logwarp.Std().Infoln("xiaZhu")
	if niuniu.Stat != StatusXiaZhu || player.Eq(niuniu.ZhuangZhu) {
		player.SendException(server.InvalidOptException, MessageTypeXiaZhu)
		return
	}
	var zhu XiaZhuEvent
	if err := server.MsgBodyParser(msg.Data, &zhu); err != nil {
		player.SendException(server.InvalidOptException, MessageTypeXiaZhu)
		return
	}

	logwarp.Std().Infoln("xiaZhu")
	if _, exist := niuniu.Stake[player.Id]; exist {
		player.SendException(server.InvalidOptException, MessageTypeXiaZhu)
	}
	niuniu.Stake[player.Id] = zhu.Socre
	niuniu.Broadcast(player, MessageTypeXiaZhu, &XiaZhuEvent{Id: player.Id, Socre: zhu.Socre})
	if niuniu.players.Len()-1 == len(niuniu.Stake) {
		logwarp.Std().Infoln(niuniu.Stake)
		niuniu.Stat = StatusFaPai
		niuniu.faPai()
	}
}

func (niuniu *NiuNiu) faPai() {
	logwarp.Std().Infoln("faPai")
	if niuniu.Stat != StatusFaPai {
		return
	}
	var niuniuPai = make(map[Pai]string)
	for k, v := range NiuNiuPai {
		niuniuPai[k] = v
	}
	// 给每个玩家发牌
	niuniu.players.Foreach(func(s string, player *server.Player) {
		// 生成随机牌
		holdingPais := RandomGenPai(niuniuPai, 5)
		paiEvent := &PaiEvent{}
		for k, _ := range holdingPais {
			paiEvent.Pais = append(paiEvent.Pais, string(k))
		}
		logwarp.Std().Debugln(player, holdingPais)
		player.Unicast(paiEvent, MessageTypeFaPai)
		niuniu.PlayerHoldingPais[player.Id] = holdingPais
	})
	niuniu.Stat = StatusChuPai
}

func (niuniu *NiuNiu) chuPai(player *server.Player, msg *server.Message) {
	logwarp.Std().Infoln("chuPai")
	if niuniu.Stat != StatusChuPai {
		player.SendException(server.InvalidOptException, MessageTypeChuPai)
		return
	}
	var chuPaiEvent ChuPaiEvent
	if err := server.MsgBodyParser(msg.Data, &chuPaiEvent); err != nil {
		player.SendException(server.InvalidOptException, MessageTypeChuPai)
		return
	}
	chuPai := NewPaisFromStringSlice(chuPaiEvent.Pais)
	// 验证牌是否合法
	// 1 重复提交判断
	logwarp.Std().Debugln(chuPai)
	if _, exist := niuniu.ChuPai[player.Id]; exist {
		player.SendException(server.InvalidOptException, MessageTypeChuPai)
		return
	}
	// 2 出牌的数量对不对
	logwarp.Std().Infoln("chuPai() 3", len(chuPai))
	if len(chuPai) != 3 {
		player.SendException(server.InvalidOptException, MessageTypeChuPai)
		return
	}
	// 3 是否拥有这些牌
	logwarp.Std().Infoln("chuPai() 4", niuniu.PlayerHoldingPais)
	holdingPais := niuniu.PlayerHoldingPais[player.Id]
	var totalValue int
	for _, cpai := range chuPai {
		if _, exist := holdingPais[cpai]; !exist {
			player.SendException(server.InvalidOptException, MessageTypeChuPai)
			return
		}
		totalValue += cpai.Value()
	}

	chuPaisMap := PaisMaping(chuPai)
	surplusPaiMap := make(map[Pai]string)
	for hpai, text := range holdingPais {
		if _, exist := chuPaisMap[hpai]; !exist {
			surplusPaiMap[hpai] = text
		}
	}
	niuniu.ChuPai[player.Id] = &ChuPaiCombination{
		ChuPaiMap:     chuPaisMap,
		SurplusPaiMap: surplusPaiMap,
	}
	niuniu.niuCheck(totalValue, niuniu.ChuPai[player.Id], surplusPaiMap, holdingPais)
	// 群推出牌消息通知
	niuniu.Broadcast(player, MessageTypeChuPai, &ChuPaiNoticeEvent{
		Id: player.Id,
	})
	if len(niuniu.ChuPai) == niuniu.players.Len() {
		// 进入最后结算阶段
		niuniu.Stat = StatusJieSuan
		niuniu.JieSuan()
	}
}

func (h *NiuNiu) JieSuan() {
	logwarp.Std().Infoln("JieSuan")
	if h.Stat != StatusJieSuan {
		return
	}

	zhuangZhu := h.ZhuangZhu
	zhuangZhuChuPaiCombination := h.ChuPai[zhuangZhu.Id]
	zhuangZhuHoldPais := h.PlayerHoldingPais[zhuangZhu.Id]

	h.players.Foreach(func(id string, player *server.Player) {
		if player.Eq(zhuangZhu) {
			return
		}
		playerChuPaiCombination := h.ChuPai[id]
		playerHoldPais := h.PlayerHoldingPais[id]
		playerXiaZhu := h.Stake[id]
		// 牛n 进行比较
		res := h.NiuNiuCompare(zhuangZhuChuPaiCombination, playerChuPaiCombination)
		if res > 0 { // 庄家得分
			zhuangZhu.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu))
			player.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu) * -1)
			zhuangZhu.Win()
			return
		} else if res < 0 { // 玩家得分
			zhuangZhu.AddScore(h.scoreCount(playerChuPaiCombination.Niu, playerXiaZhu) * -1)
			player.AddScore(h.scoreCount(playerChuPaiCombination.Niu, playerXiaZhu))
			player.Win()
			return
		}
		// 最大牌比较
		playerMaxPai := PaisNoMax(PaisMapToKeys(playerHoldPais))
		zhuangZhuMaxPai := PaisNoMax(PaisMapToKeys(zhuangZhuHoldPais))
		if playerMaxPai.No() > zhuangZhuMaxPai.No() { // 玩家得分
			zhuangZhu.AddScore(h.scoreCount(playerChuPaiCombination.Niu, playerXiaZhu) * -1)
			player.AddScore(h.scoreCount(playerChuPaiCombination.Niu, playerXiaZhu))
			player.Win()
			return
		} else if playerMaxPai.No() < zhuangZhuMaxPai.No() { // 庄家得分
			zhuangZhu.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu))
			player.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu) * -1)
			zhuangZhu.Win()
			return
		}
		if !h.Bihuase { // 庄家得分
			zhuangZhu.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu))
			player.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu) * -1)
			zhuangZhu.Win()
			return
		}
		// 花色比较
		if zhuangZhuMaxPai.HuaSeVal() > playerMaxPai.HuaSeVal() { // 庄家得分
			zhuangZhu.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu))
			player.AddScore(h.scoreCount(zhuangZhuChuPaiCombination.Niu, playerXiaZhu) * -1)
			zhuangZhu.Win()
		} else { // 玩家得分
			zhuangZhu.AddScore(h.scoreCount(playerChuPaiCombination.Niu, playerXiaZhu) * -1)
			player.AddScore(h.scoreCount(playerChuPaiCombination.Niu, playerXiaZhu))
			player.Win()
		}
	})
	var jieSuanEvent JieSuanEvent
	h.players.Foreach(func(id string, player *server.Player) {
		holdingPais := PaisMapToStringKeys(h.PlayerHoldingPais[id])
		logwarp.Std().Debugln("PlayerHoldingPais", holdingPais)
		jiesuanEntry := PlayerJieSuanEntry{
			Id:          id,
			HoldingPais: holdingPais,
			Niu:         h.ChuPai[id].Niu,
			Score:       player.CurrentScore,
		}
		logwarp.Std().Debugln("PlayerJieSuanEntry : ", jiesuanEntry)
		jieSuanEvent.PlayerJieSuanData = append(jieSuanEvent.PlayerJieSuanData, jiesuanEntry)
	})
	// 更新数据
	h.UpdateUserData()
	// 更新用户排行
	h.UpdateUserRank()

	if h.Round >= h.MaxRound {
		h.players.Foreach(func(id string, player *server.Player) {
			jieSuanEvent.PlayerZongJieSuanData = append(jieSuanEvent.PlayerZongJieSuanData, PlayerZongJieSuanEntry{
				Id:         id,
				TotalScore: player.RoundOfScore,
			})
		})
		h.Broadcast(nil, MessageTypeJieSuan, &jieSuanEvent)
		h.gameover()
		return
	}
	h.Broadcast(nil, MessageTypeJieSuan, &jieSuanEvent)
	h.nextRound()
	if h.Round == 1 {
		logwarp.Std().Infoln("扣卡", h.CostCards)
		uc, _ := server.GetUsersClubsByUidAndClubId(h.Owner.Id, h.Owner.UserInfo.CurrentClubId)
		if uc != nil {
			server.DBEngine.ID(uc.Id).Decr("house_cards", h.CostCards).Update(&server.CurrentClub{})
			server.DBEngine.ID(h.Owner.Id).Incr("cost_house_cards", h.CostCards).Update(&server.User{})
		}
	}
}

func (h *NiuNiu) nextRound() {
	h.Round++
	h.Stat = StatusWaiting
	h.ZhuangZhu = nil
	h.ReadyPlayers = server.NewPlayerMap()
	h.Stake = make(map[string]int)
	h.QiangZhuangPlayer = make([]*server.Player, 0)
	h.PlayerHoldingPais = make(map[string]map[Pai]string)
	h.ChuPai = make(map[string]*ChuPaiCombination)
	h.players.Foreach(func(s string, player *server.Player) {
		player.NextRound()
	})
	h.Broadcast(nil, MessageTypeNextRound, nil)
}

func (h *NiuNiu) gameover() {
	logwarp.Std().Infoln("gameOver")
	if h.Stat != StatusJieSuan {
		return
	}
	h.Stat = StatusDestroy
	h.gameOver = true
	h.Broadcast(nil, MessageTypeGameOver, nil)
	h.killAllPlayer()
}
func (h *NiuNiu) UpdateUserRank() error {
	logwarp.Std().Debugln("UpdateUserRank")
	redisConn := server.RedisPool.Get()
	h.players.Foreach(func(s string, player *server.Player) {
		key := fmt.Sprintf("%s_%s", "CLUBRANK", player.CurrentClub.ClubId)
		logwarp.Std().Debugln("score", player.UserInfo.Score, key)
		logwarp.Std().Debugln("id", player.Id)
		if _, err := redis.Int(redisConn.Do("ZADD", key, player.UserInfo.Score, player.Id)); err != nil {
			logwarp.Std().Warningln(err)
		}
	})
	return nil
}

func (h *NiuNiu) UpdateUserData() error {
	var sqlSession *xorm.Session

	h.players.Foreach(func(id string, player *server.Player) {
		sqlSession = server.DBEngine.ID(id)
		switch {
		case player.CurrentScore > 0:
			sqlSession.Incr("score", math.Abs(float64(player.CurrentScore)))
		case player.CurrentScore < 0:
			sqlSession.Decr("score", math.Abs(float64(player.CurrentScore)))
		}
		sqlSession.Incr("games_innings").Incr("niuniu_innings")
		if player.Wins > 0 {
			sqlSession.Incr("games_win_innings", player.Wins).Incr("niuniu_win_innings", player.Wins)
		}
		if _, err := sqlSession.Update(&server.User{}); err != nil {
			logwarp.Std().Warningln(err)
			return
		}
	})
	h.players.Foreach(func(id string, player *server.Player) {
		sqlSession = server.DBEngine.ID(id)
		switch {
		case player.CurrentScore > 0:
			sqlSession.Incr("score", math.Abs(float64(player.CurrentScore)))
		case player.CurrentScore < 0:
			sqlSession.Decr("score", math.Abs(float64(player.CurrentScore)))
		}
		sqlSession.Incr("games_innings").Incr("niuniu_innings")
		if player.Wins > 0 {
			sqlSession.Incr("games_win_innings", player.Wins).Incr("niuniu_win_innings", player.Wins)
		}
		if _, err := sqlSession.Update(&server.User{}); err != nil {
			logwarp.Std().Warningln(err)
			return
		}
	})
	// 更新所有玩家信息
	h.players.Foreach(func(id string, player *server.Player) {
		if err := player.UpdateUserInfo(); err != nil {
			logwarp.Std().Warningln(err)
			return
		}
	})
	// 胜率计算
	h.players.Foreach(func(id string, player *server.Player) {
		u := player.UserInfo
		if u.GamesInnings > 0 {
			b := decimal.NewFromFloat(float64(u.GamesInnings))
			a := decimal.NewFromFloat(float64(u.NiuNiuWinInnings))
			v := a.DivRound(b, 2).Mul(decimal.NewFromFloat(100)).IntPart()
			if _, err := server.DBEngine.ID(id).Update(&server.User{NiuNiuWinRate: int(v)}); err != nil {
				logwarp.Std().Warningln(err)
				return
			}
		}
	})
	// 更新所有玩家信息
	h.players.Foreach(func(id string, player *server.Player) {
		if err := player.UpdateUserInfo(); err != nil {
			logwarp.Std().Warningln(err)
			return
		}
	})
	return nil
}

func (h *NiuNiu) scoreCount(niun, zhu int) int {
	return 1 * zhu * NiuNRateMap[niun]
}

func (h *NiuNiu) NiuNiuCompare(a *ChuPaiCombination, b *ChuPaiCombination) int {
	return a.Niu - b.Niu
}

func (h *NiuNiu) jinniuCheck(holdingPais map[Pai]string) bool {
	for pai, _ := range holdingPais {
		if !pai.IsHua() {
			return false
		}
	}
	return true
}

func (h *NiuNiu) yinniuCheck(holdingPais map[Pai]string) bool {
	for pai, _ := range holdingPais {
		if pai.Value() != 10 {
			return false
		}
	}
	for pai, _ := range holdingPais {
		if pai.No() == 10 {
			return true
		}
	}
	return false
}

func (h *NiuNiu) niuCheck(
	totalValue int,
	cpc *ChuPaiCombination,
	surplusPaiMap, holdingPais map[Pai]string,
) {
	// 牌型组合
	if totalValue != 10 && totalValue != 20 && totalValue != 30 { // 没牛
		cpc.Niu = MeiNiu
		return
	}
	var totalVal int
	for p, _ := range surplusPaiMap {
		totalVal += p.Value()
	}
	tv := totalVal / 1 % 10 // 获取个位
	if tv != 0 {
		cpc.Niu = tv
		return
	}
	if h.Jinniu && h.jinniuCheck(holdingPais) {
		cpc.Niu = JinNiu12
		return
	}
	if h.Yinniu && h.yinniuCheck(holdingPais) {
		cpc.Niu = YinNiu11
		return
	}
	cpc.Niu = NiuNiu10
}

func (niuniu *NiuNiu) Play(player *server.Player, msg *server.Message) error {
	niuniu.mutex.Lock()
	defer niuniu.mutex.Unlock()
	switch msg.Header.MessageType {
	case MessageTypeReadyEvent:
		niuniu.ready(player)
	case MessageTypeGameStart:
		niuniu.gameStart(player)
	case MessageTypePlayerQuit:
		niuniu.playerQuit(player)
	case MessageTypeGameDestroy:
		niuniu.gameDestroy(player)
	case MessageTypeQiangZhuang:
		niuniu.qiangZhuang(player)
	case MessageTypeXiaZhu:
		niuniu.xiaZhu(player, msg)
	case MessageTypeChuPai:
		niuniu.chuPai(player, msg)
	case MessageTypeGameExit:
		niuniu.gameExitVote(player, msg)
	default:
	}
	return nil
}

func (h *NiuNiu) gameExitVote(player *server.Player, msg *server.Message) {
	logwarp.Std().Infoln("gameExitVote")
	if _, exist := h.GameExitBallotBox[player.Id]; exist {
		player.SendException(server.InvalidOptException, MessageTypeGameExit)
		return
	}
	var vote GameExitVote
	if err := server.MsgBodyParser(msg.Data, &vote); err != nil {
		player.SendException(server.InvalidOptException, MessageTypeGameExit)
		return
	}
	logwarp.Std().Debugln("vote", vote)

	if vote.YesOrNo != "yes" && vote.YesOrNo != "no" {
		player.SendException(server.InvalidOptException, MessageTypeGameExit)
		return
	}
	h.GameExitBallotBox[player.Id] = vote.YesOrNo
	h.Broadcast(nil, MessageTypeGameExit, &GameExit{Id: player.Id, YesOrNo: vote.YesOrNo})
	var (
		yesN int
		NoN  int
	)
	for _, yesOrNo := range h.GameExitBallotBox {
		switch yesOrNo {
		case "yes":
			yesN++
		case "no":
			NoN++
		}
	}
	if (h.players.Len() <= 3 && yesN == h.players.Len()) || (h.players.Len() > 3 && yesN == h.players.Len()-1) {
		h.Stat = StatusDestroy
		// 给其他玩家发送退出消息
		h.Broadcast(nil, MessageTypeGameDestroy, nil)
		h.gameOver = true
		h.killAllPlayer()
	} else if yesN+NoN == h.players.Len() { // 重置继续游戏
		h.GameExitBallotBox = make(map[string]string)
		h.Broadcast(nil, MessageTypeGameContinue, nil)
	}
}

func (niuniu *NiuNiu) Broadcast(skip *server.Player, mt uint16, data interface{}) error {
	niuniu.players.Foreach(func(s string, player *server.Player) {
		if player.Eq(skip) {
			return
		}
		if err := player.Unicast(data, mt); err != nil {
			logwarp.Std().Warningln(err)
		}
	})
	return nil
}

func (niuniu *NiuNiu) Players() []*server.Player {
	var retval []*server.Player
	niuniu.players.Foreach(func(s string, player *server.Player) {
		retval = append(retval, player)
	})
	return retval
}

func (niuniu *NiuNiu) GameOver() bool {
	return niuniu.gameOver
}
func (niuniu *NiuNiu) LivePlayersN() int { return niuniu.players.Len() }

func (niuniu *NiuNiu) GetOwner() *server.Player { return niuniu.Owner }
